#!/usr/bin/python2.7
# coding=utf-8

import time
import struct
import numpy as np


# Texts for modes 
MODE_TXT = (
 "Okänt läge!", 
 "Uppstart", 
 "Inställlningar - installation", 
 "Inställningar - drift",
 "Vänteläge",
 "Dragläge")


class avgBuffer():
  def __init__(self, v, len):
    self.buf = [v/len]*len
    self.ind = 0
    self.len = len * 1.0
    self.sum = v

  def add(self, v):
    if self.ind >= self.len:
      self.ind = 0
    x = v / self.len
    self.sum += x - self.buf[self.ind]
    self.buf[self.ind] = x
    self.ind += 1


class Parameter():
  """ Represents a parameter definition. This corresponds to the
  parameters used by the Arduino . """
  
  data_type = np.dtype([    # Data type definition of raw data
    ("index", "B"),
    ("val", ">h"),
    ("low", ">h"),
    ("high", ">h"),
    ("low_map", ">h"),
    ("high_map", ">h"),
    ("step", ">h"),
    ("descr", "S21")])

  def __init__(self):
    self.data = None
    
    self.mode = 0
    self.index = 0
    self.val = 0
    self.low = 0
    self.high = 1
    self.low_map = 0
    self.high_map = 1
    self.step = 1
    self.descr = ""
    
  def Parse(self, m, data):
    """ Interpret the raw bytes received from the Arduino, and copy values to 
    the Parameter properties accordingly. """
    
    self.data       = np.frombuffer(buffer=data, dtype=Parameter.data_type, count=1, offset=0)[0]

    self.mode	    = m
    self.index	  = self.data[0]
    self.val	    = self.data[1]
    self.low	    = self.data[2]
    self.high	    = self.data[3]
    self.low_map  = self.data[4]
    self.high_map = self.data[5]
    self.step	    = self.data[6]

    s = self.data[7]
    s = s.replace('\x00', '')
    s = s.replace('\x80', 'å')
    s = s.replace('\xE1', 'ä')
    s = s.replace('\xEF', 'ö')
    s = s.replace('\x81', 'Å')
    s = s.replace('\x82', 'Ä')
    s = s.replace('\x83', 'Ö')
    self.descr = s
    
  def mapval(self, v):
    """ Returns a value v mapped from (low,hi) to (low_map,high_map) 
    using linear interpolation. If high=low method returns 0."""        
    if self.high==self.low:
      return 0
    return self.low_map + (v-self.low)*(self.high_map-self.low_map)*1.0/ ((self.high-self.low)*1.0)

  def __str__(self): 
    """ Returns a string representation of the parameter. """
    s = "Parameter - no %d\n" % (self.index+1)
    s += "  descr   : %s\n" % self.descr
    s += "  mode    : %s (%d)\n" % (MODE_TXT[self.mode], self.mode)
    s += "  val     : %4d (%4d)\n" % (self.mapval(self.val),self.val)
    s += "  low     : %4d (%4d)\n" % (self.mapval(self.low),self.low)
    s += "  high    : %4d (%4d)\n" % (self.mapval(self.high),self.high)
    s += "  low_map : %4d\n" % self.low_map
    s += "  high_map: %4d\n" % self.high_map
    s += "  step    : %4d (%4d)\n" % (self.mapval(self.step), self.step)
    return s
    
    
class Sample():
  """ Represents a sample as received when sending a get request to the 
  Arduino. """

  data_type = np.dtype([    # Data type definition of raw data
    ("time",      ">u4"),
    ("tach_pump", "|u1"),
    ("tach_drum", "|u1"),
    ("temp",      ">i2"),
    ("pres",      ">i2")])
  pump_buffer = avgBuffer(0,10) # Average for tacho pump 
  drum_buffer = avgBuffer(0,10) # Average for tacho drum 
  time_buffer = avgBuffer(200,20) # Average for sample period 
  _tx_time  = [0,0]   # Transmitting times
  _rx_time  = [0,0]   # Recieving times
  _dt = 0
  p_pump = Parameter()
  p_drum = Parameter()
  p_temp = Parameter()
  p_pres = Parameter()
  
  def __init__(self):
    self.data = None
    self.mode = 0
    self.time = 0
    self.tach_pump = 0
    self.tach_drum = 0
    self.temp = 0
    self.pres = 0
    
  def Parse(self, m, data):
    """ Interpret raw bytes as sent from the Arduino representing a sample. """
    self.mode       = m
    self.data       = np.frombuffer(buffer=data, dtype=Sample.data_type, count=1, offset=0)[0]
    
    self.time       = self.data[0]
    self.tach_pump  = Sample.p_pump.mapval(self.data[1])
    self.tach_drum  = Sample.p_drum.mapval(self.data[2])
    self.temp       = Sample.p_temp.mapval(self.data[3])
    self.pres       = Sample.p_pres.mapval(self.data[4])

    Sample._tx_time[0]  = Sample._tx_time[1]
    Sample._tx_time[1]  = self.time
    Sample._rx_time[0]  = Sample._rx_time[1]
    Sample._rx_time[1]  = time.time()
    
    Sample.pump_buffer.add(self.tach_pump)
    Sample.drum_buffer.add(self.tach_drum)
    Sample._dt = (Sample._rx_time[1] - Sample._rx_time[0])*1000.0
    Sample.time_buffer.add(Sample._dt)

  def __str__(self): 
    s = "Sample - receive period %d(%3.1f)\n" % (Sample._dt, Sample.time_buffer.sum)
    s += "  mode       : %d (%s)\n" % (self.mode, MODE_TXT[self.mode])
    s += "  on time    : %d ms\n" % self.time
    s += "  pump speed : %3d(%3d) rpm\n" % (self.tach_pump, Sample.pump_buffer.sum)
    s += "  drum speed : %3d(%3d) rpm\n" % (self.tach_drum, Sample.drum_buffer.sum)
    s += "  period     : %3d ms\n" % (Sample._tx_time[1] - Sample._tx_time[0])
    s += "  temperature: %3d deg C\n" % self.temp
    s += "  pressure   : %3d bar\n" % self.pres
    return s

